Chapter 16 Python Exceptions
Note the following:-
16.2. Basic concepts of exceptions in Python
16.2.1. Errors versus exceptions
Take example of a simple IndexError which is raised when you try to access an index of a sequence which is larger than the largest index in the sequence. This will be clear from the following code:
This script is available on page 393 of the book
>>> myS = 'abcd'
>>> myS[4]
Traceback (most recent call last):
File "<pyshell#17>", line 1, in<module>
myS[4]
IndexError: string index out of range
16.2.2. The raise statement
In the example given above, it was the Python interpreter which raised the exception. However a programmer can himself raise an exception by using the raise statement. An exception can have an argument, which is a value that gives additional information about the problem. The content of the argument vary with exception. Syntax for exception statement is as follows (Note that the argument is optional):
raise [exceptionName[, argument]]
This is clarified in the following example:
>>>raise NameError('I raised this error')
Traceback (most recent call last):
File "<pyshell#18>", line 1, in<module>
raise NameError('I raised this error')
NameError: I raised this error
>>>
16.2.3 The try-except-else block of code in Python
If there is a piece of code which might throw an exception, then the good way to deal with this code would be to enclose it in a try block of code. The try block gives the programmer an opportunity to deal with the error. So if you have a try block and you get an error, the interpreter will give you an opportunity to deal with it in the except block. However if the script in the except block is unable to deal with the error, then the Python interpreter will stop the execution of the script and raise an in-built error. When you enclose a suspicious code in a try block, there are following two possibilities:
You can deal with this as shown in the following code: This script is available on page 394 of the book
# Pseudo code
try:
# Code with bugs... could throw exception
except(Exception1[, Exception2 [, ... ExceptionN]]):
# If any of above exceptions ie Exception1 to
# ... ExceptionN occur, execute this block
else:
# If there is no exception in the list of exceptions, then execute this code
You can use a try-except block (without else-finally) also to ensure that a user gives a particular type of input. For example, if you want the user to only give an integer, you may write code as follows on IDLE:
This script is available on page 395 of the book
while True:
try:
myI = int(input("Give an integer.."))
print("You gave integer... so quit")
break
except ValueError:
print("Not an integer... try again")
16.2.4. The try-except-else-finally block with multiple except and except with no exception type
Note that when you are writing the except block you have the following three options:
So for except block with one type of exception, the block is executed only if that type of exception is raised. For except block with multiple exceptions, the except block is executed if any of the exceptions in the exceptions given is raised. For except block with no exception, the block is executed if there is any type of exception. This will become clear with the following example:
This script is available on page 396 of the book
def myFunc(myL, idx, divident, divisor):
try:
print('If this is printed, index OK->',myL[idx])
print('If this printed, divisor not 0->',divident/ divisor)
except IndexError:
print('Index is out of range')
except ZeroDivisionError:
print(' Cant divide by 0')
else:
print('No exception raised')
finally:
print('Exception or not, this will be printed')
The above code has been saved in a file named test4.py. You can execute this code with different inputs as shown:
This script is available on page 397 of the book
>>>import test4
>>> L = ['a', 'b', 'c', 'd']
>>> test4.myFunc(L, 2, 8, 2)
If this is printed, index OK-> c
If this printed, divisor not 0->4.0
No exception raised
Exception or not, this will be printed
>>> test4.myFunc(L,5,8,2)
Index is out of range
Exception or not, this will be printed
>>> test4.myFunc(L,2,8,0)
If this is printed, index OK-> c
Cant divide by 0
Exception or not, this will be printed
>>>
You can slightly modify the above program and introduce a line of code which throws an exception which is not dealt with in the except block as follows (note the code is saved as a module test4.py).
This script is available on page 398 of the book
def myFunc(myL, idx, divident, divisor):
try:
print('If this is printed, index OK',myL[idx])
print('If this printed, divisor not 0',divident/ divisor)
print(myL + idx) # Adding list myL to int idx raises TypeError
# .. which is not handled in except block
except IndexError:
print('Index is out of range')
except ZeroDivisionError:
print(' Cant divide by 0')
else:
print('No exception raised')
finally:
print('Exception or not, this will be printed')
The new line of code added is
print(myL + idx)
It contains a print function which prints the result of addition of myL with idx. Now myL is a list object while idx is an integer object and the two cannot be added. So this line of code will throw an error of type TypeError, but this type of error is not handled in the two except blocks. So what will happen. First the finally block of code will be executed, and second the script will raise the built-in error of type TypeError as follows:
This script is available on page 399 of the book
>>>import test4
>>> L = [1, 2, 3, 4]
>>> test4.myFunc(L, 1, 4, 2)
If this is printed, index OK 2
If this printed, divisor not 0 2.0
Exception or not, this will be printed
Traceback (most recent call last):
File "<pyshell#2>", line 1, in<module>
test4.myFunc(L, 1, 4, 2)
File "C:/Users/ADG/AppData/Local/Programs/Python/Scripts\test4.py", line 5, in myFunc
print(myL + idx) # Adding list myL to int idx raises TypeError
TypeError: can only concatenate list (not"int") to list
>>>
You can modify the above code to take care of the TypeError generated (this script is saved as test5.py):
This script is available on page 399 of the book
def myFunc(myL, idx, divident, divisor):
try:
print('If this is printed, index OK',myL[idx])
print('If this printed, divisor not 0',divident/ divisor)
print(myL + idx) # Adding list myL to int idx raises TypeError
# .. which is not handled in except block
except IndexError: # Catches only IndexError
print('Index is out of range')
except ZeroDivisionError: # Catches only ZeroDivisionError
print(' Cant divide by 0')
except: # Catches ALL exceptions
print('All errors are now taken care of')
else:
print('No exception raised')
finally:
print('Exception or not, this will be printed')
You may execute this file (By importing test5.py and then using its myFunc()) as follows:
>>> myFunc([1,2,3], 2, 4, 1)
If this is printed, index OK 3
If this printed, divisor not 04.0
All errors are now taken care of
Exception or not, this will be printed
>>>
16.2.5. Using try-except block to read a file
Reading a file may not always be successful leading to errors and therefore premature termination of the program. You can write a function which reads a file and if there is an error, it catches the exception. The function has
This script is available on page 400 of the book
def myReader(fileName):
try:
with open(fileName, 'r+') as f:
fContent = f.read()
print(fContent)
except IOError:
print("Something wrong")
else:
print("ok")
# Call the function
fName = input("Give file name:- ")
myReader(fName)
16.3. User-defined exceptions
Sometimes a user needs to create his own exceptions. Python allows the user to derive his own exceptions. In older Python versions, there were following two ways in which exceptions could be derived.
But from Python 2.6 onwards, it is possible to derive exceptions only from exception class and not from string class.
Nowadays, if you want to create a user-defined exception then you have to derive or inherit it from Exception class or from a class which in turn has been inherited from some exception class.
This script is available on page 401 of the book
class MyError(Exception):
print("User Exception")
# raise exception MyError
try:
print("entering the try block")
raise MyError
print("This is not printed")# This line is never executed
except MyError:
print("raised")
16.4.1. An except clause may name multiple exceptions as a parenthesized tuple
From Python 3.x onwards, you may raise a number of exceptions with the same except statement. But the exceptions must be in the form of a “parenthesized tuple”.
Suppose you have some errors, say RuntimeError, TypeError and NameError, then you could raise these three errors in a single except as a “tuple of errors” as follows:
except (RuntimeError, TypeError, NameError):
pass
16.7. Beyond text book
See Page 403 of the book
16.7.1. The exception hierarchy
In Python, exceptions also have a class hierarchy. Study the hierarchy of exceptions available at: https://docs.python.org/3/library/exceptions.html#exception-hierarchy . After studying the class hierarchy of exceptions, you may realize the following:
The important things to understand about hierarchy of exceptions are that:-
This is demonstrated in the following code:
This script is available on page 404 of the book
class A(Exception):
pass
class B(A):
pass
# Raise B before A
print('Raise B before A')
for x in [A, B]:
try:
raise x()
except B:
print('Catch B') # Cant catch A. So A can be reached
except A:
print('Catch A')
# Raise A before B
print('Raise A before B')
for x in [A, B]:
try:
raise x()
except A: # Can catch B. So B never reached.
print('Catch A')
except B:
print('Catch B')
16.7.2. The exception object
As pointed out in the previous section, all exceptions are classes in Python, so instances of exceptions are objects. You can access the exception objects as follows:
Note that in the above code, the error_object will refer to one of the three error classes given in the tuple because only one of the three errors can be raised at one time. Further, the lifetime of this error_object is only the indented block of code following the except statement.
Within this block you can use this error_object. The following code shows the use of this error_object (by convention this error_object is often called e)
This script is available on page 405 of the book
try:
x = 1/0
except ZeroDivisionError as e:
print('type of e->', type(e))
print('arguments of e->', e.args)
print('string representation of e->', str(e))
16.8. Assignment – studying the traceback module
You need to understand that when an exception is thrown in a Python script, the following happen:-
sys.exc_info() function of the sys module. (How to “access” the Traceback object using sys.exc_info() function is explained below).sys.exc_info() function), then you can access its various attributes/ methods by using functions of the traceback module. (Note we use “Traceback” to refer to an object of this class and “traceback” to refer to the traceback module).sys and (2) traceback, you can “access” the Traceback object created and also “access” certain attributes of this Traceback object.tb) is different from the traceback module. The Traceback object (that is tb) will always be “implicitly created” when an exception is thrown. On the other hand the traceback module is a module provided to programmers to extract relevant information out of the Traceback object tb. So a Traceback object is created by the Python interpreter whenever an exception is thrown. On the otherhand a programmer may use the traceback module to “do something” with a Traceback object.So to do this assignment you need to:-
sys.exc_inf() function inside the exception handler. Python has a module named traceback. As per the official documentation , “This module provides a standard interface to extract, format and print stack traces of Python programs”.
The assignment is to study this module and use it for getting information about exceptions raised.
Python also has a sys module which has a sys.exc_info() method. Together these two modules can be used to get information about the exception being handled.
The signature of the sys.exe_info() method on Jupyter is:
Docstring:
exc_info() -> (type, value, traceback)
Return information about the most recent exception caught by an except clause in the current stack frame or in an older stack frame.
Type: builtin_function_or_method
So this method returns a tuple of three values namely (1) type (2) value and (3) traceback
It is this Traceback object which is of interest because it “encapsulates” the call stack at the point where the exception originally occurred. It is this third parameter (index 2) which will be used with the traceback module.
The following script shows how the sys.exc_info() method is used:
This script is available on page 407 of the book
import sys
try:
a = 1/0
except ZeroDivisionError as e:
exc_tup = sys.exc_info()
except_type = exc_tup[0]
print('exception type->', except_type)
except_value = exc_tup[1]
print('exception value->', except_value)
except_obj = exc_tup[2]
print('exception object type->', type(except_obj))
print('exception object->', except_obj)
Now coming to the module traceback, it has a function traceback.extract_tb().
This function takes as its argument a traceback object (which is the third parameter of the tuple returned by sys.exc_info() and generally called “tb”). The signature of this function is as follows:
Signature: traceback.extract_tb(tb, limit=None)
Docstring:
Return list of up to limit pre-processed entries from traceback.
A pre-processed stack trace entry is a quadruple (filename, line number, function name, text) representing the information that is usually printed for a stack trace. The text is a string with leading and trailing whitespace stripped; if the source is not available it is None.
So out of the four stack trace entries, you will get entries at index 0, 1 and 2 but the entry at index 3 may or may not exist (since it can be None).
The following script is an example of how sys.exc_info() and traceback.extract_tb can be used to get information about the exception caught by the except clause.
This script is available on page 408 of the book
import sys
import traceback
try:
x = 1/0
except ZeroDivisionError as e:
# Get current system exception
e_type, e_value, e_tb = sys.exc_info()
print('e_type->', e_type)
print('e_value->', e_value)
# Use extraxt_tb() to
tb_stack = traceback.extract_tb(e_tb)
for tb_frame in tb_stack:
print('tb_frame->', tb_frame)
print('type of tb_frame->', type(tb_frame))
func_name = tb_frame[2]
lineno = tb_frame[1]
filename = tb_frame[0]
print('func_name->', func_name)
print('lineno->', lineno)
print('filename->', filename)
# You can also use traceback.extract_stack() to get info about the error
print(traceback.extract_stack())